package monitor

import (
	"bytes"
	"context"
	"gitee.com/igolang/imoney/internal/app/system/entities"
	"gitee.com/igolang/imoney/internal/app/system/entities/constants"
	"gitee.com/igolang/imoney/internal/app/system/services/dict"
	"gitee.com/igolang/imoney/internal/app/system/tools"
	commonEntities "gitee.com/igolang/imoney/internal/common/entities"
	"gitee.com/igolang/imoney/internal/common/httpResp"
	commonTools "gitee.com/igolang/imoney/internal/common/tools"
	"gitee.com/igolang/imoney/internal/common/tools/cronJob"
	"gitee.com/igolang/imoney/internal/core/client"
	"gitee.com/igolang/imoney/internal/core/logger"
	"gitee.com/igolang/imoney/internal/models/dao"
	"gitee.com/igolang/imoney/internal/models/model"
	"gitee.com/igolang/pkg/xgorm"
	"gitee.com/igolang/pkg/xtime"
	"gitee.com/igolang/pkg/xutils"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/robfig/cron/v3"
	"github.com/spf13/cast"
	"gorm.io/gen"
	"net/http"
	"time"
)

var Job = &job{}

type job struct{}

func (s *job) List(ctx *gin.Context) (*commonEntities.ListResp, error) {
	var req entities.JobPageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	req.Page()
	conds := s.formatPageListConds(req)
	total, err := dao.SysJob.Where(conds...).Count()
	if err != nil {
		return nil, err
	}
	result := &commonEntities.ListResp{
		Total: total,
		Rows:  make([]*model.SysJob, 0),
	}
	if total == 0 {
		return result, nil
	}
	rows, err := dao.SysJob.Where(conds...).Limit(req.PageSize).Offset(req.Offset).Order(dao.SysJob.JobID.Desc()).Find()
	if err != nil {
		return nil, err
	}
	result.Rows = rows
	return result, nil
}

func (s *job) Detail(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	row, err := dao.SysJob.Where(dao.SysJob.JobID.Eq(id)).Take()
	if err != nil {
		return nil, err
	}
	parser := cron.NewParser(
		cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
	)
	schedule, err := parser.Parse(row.CronExpression)
	if err != nil {
		return nil, err
	}
	data := xgorm.ModelToMapWithTag(row, "json")
	data["running"] = cronJob.Running(row.InvokeTarget)
	data["nextValidTime"] = schedule.Next(time.Now()).Format(time.DateTime)
	return data, nil
}

func (s *job) Export(ctx *gin.Context) (*bytes.Buffer, error) {
	var req entities.JobPageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	req.Page()
	conds := s.formatPageListConds(req)
	rows, err := dao.SysJob.Where(conds...).Rows()
	if err != nil {
		return nil, err
	}
	header := []string{"任务序号", "任务名称", "任务组名", "调用目标字符串", "执行表达式", "并发执行", "任务状态", "任务描述"}
	data := [][]string{header}
	dc := client.DB()
	groupOption, _ := dict.Get("sys_job_group")
	statusOption, _ := dict.Get("sys_job_status")
	for rows.Next() {
		var row model.SysJob
		if dc.ScanRows(rows, &row) == nil {
			data = append(data, []string{
				cast.ToString(row.JobID),
				row.JobName,
				dict.LabelOptions(groupOption, row.JobGroup),
				row.InvokeTarget,
				row.CronExpression,
				s.formatConcurrent(row.Concurrent),
				dict.LabelOptions(statusOption, row.Status),
				row.Remark,
			})
		}
	}
	fh := xutils.CreateExcelFileWithData(data)
	defer fh.Close()
	return fh.WriteToBuffer()
}

func (s *job) Save(ctx *gin.Context) (err error) {
	var req entities.JobSaveReq
	if err = ctx.ShouldBind(&req); err != nil {
		return req.FormatErr(err)
	}
	if ctx.Request.Method == http.MethodPut && req.JobId == 0 {
		return errors.New("请指定任务")
	}
	if cronJob.Load(req.InvokeTarget) == nil {
		return errors.Errorf("保存任务'%s'失败，目标字符串对应的任务实体不存在", req.JobName)
	}
	conds := make([]gen.Condition, 0)
	index := 0
	if req.JobId > 0 {
		conds = append(conds, dao.SysJob.JobID.Neq(req.JobId))
		index = 1
	}
	conds = append(conds, dao.SysJob.JobName.Eq(req.JobName))
	if count, _ := dao.SysJob.Where(conds...).Count(); count > 0 {
		return errors.Errorf("保存任务'%s'失败，名称已存在", req.JobName)
	}
	conds[index] = dao.SysJob.InvokeTarget.Eq(req.InvokeTarget)
	if count, _ := dao.SysJob.Where(conds...).Count(); count > 0 {
		return errors.Errorf("保存任务'%s'失败，调用目标已存在", req.JobName)
	}
	if req.Status == "" {
		req.Status = "1"
	}
	row := &model.SysJob{}
	if req.JobId > 0 {
		row, err = dao.SysJob.Where(dao.SysJob.JobID.Eq(req.JobId)).Take()
		if err != nil {
			return err
		}
	}
	oldExpression := row.CronExpression
	oldKey := row.InvokeTarget

	userInfo := tools.GetUserInfo(ctx)
	row.JobName = req.JobName
	row.Concurrent = cast.ToString(req.Concurrent)
	row.CronExpression = req.Expression
	row.InvokeTarget = req.InvokeTarget
	row.JobGroup = req.JobGroup
	row.Status = req.Status
	row.Remark = req.Remark
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	if req.JobId == 0 {
		row.CreateBy = userInfo.User.UserName
		row.CreateTime = xtime.Time(time.Now())
		err = dao.SysJob.Create(row)
		if err == nil && req.Status == constants.OkStatus {
			return cronJob.AddScheduler(s.formatCronJob(row))
		}
		return err
	}
	data := xgorm.ModelToMap(row)
	_, err = dao.SysJob.Where(dao.SysJob.JobID.Eq(req.JobId)).Updates(data)
	if err == nil {
		if req.Status == constants.OkStatus {
			if oldKey != req.InvokeTarget || oldExpression != req.Expression {
				return cronJob.AddScheduler(s.formatCronJob(row))
			}
		} else {
			cronJob.RemoveScheduler(oldKey)
		}
	}
	return err
}

func (s *job) Delete(ctx *gin.Context) error {
	ids := commonTools.String2Int64s(ctx.Param("ids"))
	if len(ids) == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	rows, err := dao.SysJob.Select(dao.SysJob.InvokeTarget).Where(dao.SysJob.JobID.In(ids...)).Find()
	if err != nil {
		return err
	}
	_, err = dao.SysJob.Where(dao.SysJob.JobID.In(ids...)).Delete()
	// 从调度器中删除
	if err == nil {
		for _, v := range rows {
			cronJob.RemoveScheduler(v.InvokeTarget)
		}
	}

	return err
}

func (s *job) ChangeStatus(ctx *gin.Context) (err error) {
	var req struct {
		JobId  int64  `json:"jobId"`
		Status string `json:"status"`
	}
	if err = ctx.ShouldBind(&req); err != nil {
		return err
	}
	if req.JobId == 0 || req.Status == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	row, err := dao.SysJob.Where(dao.SysJob.JobID.Eq(req.JobId)).Take()
	if err != nil {
		return err
	}
	userInfo := tools.GetUserInfo(ctx)
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	row.Status = req.Status
	_, err = dao.SysJob.Where(dao.SysJob.JobID.Eq(req.JobId)).Updates(row)
	if err != nil {
		return err
	}
	if req.Status == constants.OkStatus {
		cronJob.AddScheduler(s.formatCronJob(row))
	} else {
		cronJob.RemoveScheduler(row.InvokeTarget)
	}

	return err
}

func (s *job) Run(ctx *gin.Context) (err error) {
	var req struct {
		JobId int64 `json:"jobId"`
	}
	if err = ctx.ShouldBind(&req); err != nil {
		return err
	}
	if req.JobId == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	row, err := dao.SysJob.Where(dao.SysJob.JobID.Eq(req.JobId)).Take()
	if err != nil {
		return err
	}
	j := s.formatCronJob(row)
	xutils.RecoverGo(func() {
		j.Run()
	}, commonTools.ErrorHandler(context.TODO(), "立即执行任务，job: %v", row))
	return nil
}

func (s *job) Init() {
	rows, err := dao.SysJob.Where(dao.SysJob.Status.Eq(constants.OkStatus)).Rows()
	if err != nil {
		logger.Log().Error("init job err", "err", err)
		return
	}
	dc := client.DB()
	for rows.Next() {
		var row model.SysJob
		if dc.ScanRows(rows, &row) == nil {
			j := s.formatCronJob(&row)
			if err = cronJob.AddScheduler(j); err != nil {
				logger.Log().Error("add job err", "err", err)
			}
		}
	}
}

func (s *job) formatCronJob(row *model.SysJob) *cronJob.Job {
	j := &cronJob.Job{
		Key:        row.InvokeTarget,
		Name:       row.JobName,
		Desc:       row.Remark,
		Spec:       row.CronExpression,
		Concurrent: row.Concurrent == constants.OkStatus,
	}
	cmdF := cronJob.Load(row.InvokeTarget)
	j.SetCmd(cmdF)
	j.SetCallback(JobLog.AddJobLog)

	return j
}

func (s *job) formatConcurrent(value string) string {
	if value == constants.OkStatus {
		return "允许"
	}
	return "禁止"
}

func (s *job) formatPageListConds(req entities.JobPageListReq) []gen.Condition {
	conds := make([]gen.Condition, 0)
	if req.JobName != "" {
		conds = append(conds, dao.SysJob.JobName.Like(`%`+req.JobName+`%`))
	}
	if req.JobGroup != "" {
		conds = append(conds, dao.SysJob.JobGroup.Eq(req.JobGroup))
	}
	if req.Status != "" {
		conds = append(conds, dao.SysJob.Status.Eq(req.Status))
	}

	return conds
}
